// ----------------------------------------------------------------------------
// Copyright (C) 2003 Rafael H. Bordini, Jomi F. Hubner, et al.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// To contact the authors:
// http://www.inf.ufrgs.br/~bordini
// http://www.das.ufsc.br/~jomi
//
//----------------------------------------------------------------------------

package jason.asSemantics;

import jason.JasonException;
import jason.RevisionFailedException;
import jason.architecture.AgArch;
import jason.asSyntax.*;
import jason.asSyntax.Trigger.TEOperator;
import jason.asSyntax.Trigger.TEType;
import jason.asSyntax.directives.FunctionRegister;
import jason.asSyntax.directives.Import;
import jason.asSyntax.directives.Include;
import jason.asSyntax.parser.ParseException;
import jason.asSyntax.parser.Token;
import jason.asSyntax.parser.TokenMgrError;
import jason.asSyntax.parser.as2j;
import jason.bb.BeliefBase;
import jason.bb.DefaultBeliefBase;
import jason.bb.DeltaBeliefBase;
import jason.functions.Count;
import jason.functions.RuleToFunction;
import jason.runtime.Settings;
import static jason.util.Util.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * The Agent class has the belief base and plan library of an AgentSpeak agent. It also implements the default selection
 * functions of the AgentSpeak semantics.
 */
public class Agent {
	
	// Members
	protected BeliefBase bb = null;
	protected PlanLibrary pl = null;
	protected TransitionSystem ts = null;
	protected String aslSource = null;
	
	private List<Literal> initialGoals = null; // initial goals in the source code
	private List<Literal> initialBels = null; // initial beliefs in the source code
	
	private Map<String, InternalAction> internalActions = null;
	private Map<String, ArithFunction> functions = null;
	
	private boolean hasCustomSelOp = true;
	
	private ScheduledExecutorService scheduler = null;
	
	protected Logger logger = Logger.getLogger(Agent.class.getName());
	
	private HashSet<PredicateIndicator> exports = null; // the result of export directives within the agent/module
	private TreeMap<String, Module> modules = null; // the modules defined within the agent/module
	private TreeMap<String, Module> modulesImported = null; // the list of imported modules in the agent hierarchy --> only one per agent hierarchy
	private PriorityQueue<Import.Importer> importQueue = null; // the list of imported modules waiting to apply the prefixes in the agent hierarchy --> only one per agent hierarchy
	private TreeSet<String> importChain = null; // the list of modules currently under import (one path in the agent hierarchy starting from the level 0 agent)
	private TreeSet<String> modulesApplied = null; // the list of already applied modules
	private TreeSet<String> modulesAppliedDelta = null; // the list of already applied modules during an atomic intention
	private int level = 0; // the level of the agent in the agent hierarchy / modularity tree
	private String importedUnderModuleName = null; // this module is imported under this pretty name
	private Module baseAgent = null; // the module representation of the base agent
	
	private boolean doImports = true;
	private boolean sideKick = false;
	
	private boolean atomic = false;
	
	protected DeltaBeliefBase dbb = null;
	
	public Agent() {
		checkCustomSelectOption();
	}
	
	/** initialise the TS and other components of the agent */
	public void initAg() {
		if (bb == null) bb = new DefaultBeliefBase();
		if (pl == null) pl = new PlanLibrary(this);
		
		if (initialGoals == null) initialGoals = new ArrayList<Literal>();
		if (initialBels == null) initialBels = new ArrayList<Literal>();
		
		if (internalActions == null) internalActions = new HashMap<String, InternalAction>();
		initDefaultFunctions();
		
		if (ts == null) ts = new TransitionSystem(this, new Circumstance(), new Settings(), new AgArch());
		
		if (exports == null) exports = new HashSet<PredicateIndicator>();
		if (modules == null) modules = new TreeMap<String, Module>();
		if (importChain == null) importChain = new TreeSet<String>();
		if (modulesApplied == null) modulesApplied = new TreeSet<String>();
		if (modulesAppliedDelta == null) modulesAppliedDelta = new TreeSet<String>();
		
		if (dbb == null) dbb = new DeltaBeliefBase(this);
	}
	
	/** parse and load the agent code, asSrc may be null */
	public void initAg(String asSrc) throws JasonException {
		initAg();
		// set the agent
		try {
			boolean parsingOk = true;
			if (asSrc != null) {
				asSrc = asSrc.replaceAll("\\\\", "/");
				setASLSrc(asSrc);
				
				if (asSrc.startsWith(Import.CRPrefix)) {
					// loads the class from a jar file (for example)
					parseAS(Agent.class.getResource(asSrc.substring(Import.CRPrefix.length())).openStream());
				} else {
					// check whether source is an URL string
					try {
						parsingOk = parseAS(new URL(asSrc));
					} catch (MalformedURLException e) {
						parsingOk = parseAS(new File(asSrc));
					}
				}
			}
			
			if (parsingOk) {
				addInitialBelsInBB();
				addInitialGoalsInTS(initialGoals);
			}
			
			// kqml Plans at the end of the ag PS
			if (JasonException.class.getResource("/asl/kqmlPlans.asl") != null) {
				setASLSrc("kqmlPlans.asl");
				parseAS(JasonException.class.getResource("/asl/kqmlPlans.asl"));
				setASLSrc(asSrc);
			} else {
				logger.warning("The kqmlPlans.asl was not found!");
			}
		} catch (ParseException e) {
			logger.log(Level.SEVERE, "as2j: parsing error in \"" + asSrc + "\": " + e.getMessage());
			throw new JasonException("as2j: parsing error in \"" + asSrc + "\": " + e);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Error creating customised Agent class!", e);
			throw new JasonException("Error creating customised Agent class! - " + e);
		}
	}
	
	public void setAsCloneForModuleSystem(Agent outerAg) {
		this.aslSource = outerAg.aslSource;
		this.exports = (HashSet<PredicateIndicator>)outerAg.exports.clone();
		this.modules = (TreeMap<String, Module>)outerAg.modules.clone();
		this.modulesImported = outerAg.modulesImported == null ? null : (TreeMap<String, Module>)outerAg.modulesImported.clone();
		this.importQueue = outerAg.importQueue == null ? null : new PriorityQueue<Import.Importer>(outerAg.importQueue);
		this.importChain = (TreeSet<String>)outerAg.importChain.clone();
		this.modulesApplied = (TreeSet<String>)outerAg.modulesApplied.clone();
		this.modulesAppliedDelta = (TreeSet<String>)outerAg.modulesAppliedDelta.clone();
		this.doImports = outerAg.doImports;
		this.importedUnderModuleName = outerAg.importedUnderModuleName;
	}
	
	public void setAsSameForModuleSystem(Agent outerAg) {
		// not clone by purpose
		
		this.aslSource = outerAg.aslSource;
		this.exports = outerAg.exports;
		this.modules = outerAg.modules;
		this.modulesImported = outerAg.modulesImported;
		this.importQueue = outerAg.importQueue;
		this.importChain = outerAg.importChain;
		this.modulesApplied = outerAg.modulesApplied;
		this.modulesAppliedDelta = outerAg.modulesAppliedDelta;
		this.doImports = outerAg.doImports;
		this.importedUnderModuleName = outerAg.importedUnderModuleName;
	}
	
	/**
	 * @deprecated Prefer the initAg method with only the source code of the agent as parameter.
	 * 
	 *             A call of this method like
	 * 
	 *             <pre>
	 *     TransitionSystem ts = ag.initAg(arch, bb, asSrc, stts)
	 * </pre>
	 * 
	 *             can be replaced by
	 * 
	 *             <pre>
	 * new TransitionSystem(ag, new Circumstance(), stts, arch);
	 * ag.setBB(bb); // only if you use a custom BB
	 * ag.initAg(asSrc);
	 * TransitionSystem ts = ag.getTS();
	 * </pre>
	 */
	public TransitionSystem initAg(AgArch arch, BeliefBase bb, String asSrc, Settings stts) throws JasonException {
		try {
			if (bb != null) setBB(bb);
			new TransitionSystem(this, new Circumstance(), stts, arch);
			initAg(asSrc);
			return ts;
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Error creating the agent class!", e);
			throw new JasonException("Error creating the agent class! - " + e);
		}
	}
	
	public void stopAg() {
		bb.stop();
		
		if (scheduler != null) scheduler.shutdownNow();
	}
	
	/**
	 * Clone BB, PL, Circumstance. A new TS is created (based on the cloned circumstance).
	 */
	public Agent clone(AgArch arch) {
		Agent a = new Agent();
		
		a.setLogger(arch);
		a.logger.setLevel(this.getTS().getSettings().logLevel());
		
		a.bb = this.bb.clone();
		a.pl = this.pl.clone();
		
		a.dbb = this.dbb.clone();
		a.atomic = this.atomic;
		
		try {
			fixAgInIAandFunctions(a);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//a.aslSource = this.aslSource; // setAsSameForModuleSystem
		a.internalActions = new HashMap<String, InternalAction>();
		a.initDefaultFunctions();
		a.setTS(new TransitionSystem(a, this.getTS().getC().clone(), this.getTS().getSettings(), arch));
		
		a.setAsCloneForModuleSystem(this);
		return a;
	}
	
	private void fixAgInIAandFunctions(Agent a) throws Exception {
		// find all internal actions and functions and change the pointer for agent
		for(Plan p : a.getPL()) {
			// search context
			if (p.getContext() instanceof Literal) fixAgInIAandFunctions(a, (Literal)p.getContext());
			
			// search body
			if (p.getBody() instanceof Literal) fixAgInIAandFunctions(a, (Literal)p.getBody());
		}
	}
	
	private void fixAgInIAandFunctions(Agent a, Literal l) throws Exception {
		// if l is internal action/function
		if (l instanceof InternalActionLiteral) {
			((InternalActionLiteral)l).setIA(null);
		}
		if (l instanceof ArithFunctionTerm) {
			((ArithFunctionTerm)l).setAgent(a);
		}
		for(int i = 0; i < l.getArity(); i++) {
			if (l.getTerm(i) instanceof Literal) fixAgInIAandFunctions(a, (Literal)l.getTerm(i));
		}
	}
	
	public void setLogger(AgArch arch) {
		if (arch != null) logger = Logger.getLogger(Agent.class.getName() + "." + arch.getAgName());
	}
	
	public Logger getLogger() {
		return logger;
	}
	
	public ScheduledExecutorService getScheduler() {
		if (scheduler == null) scheduler = Executors.newScheduledThreadPool(2);
		return scheduler;
	}
	
	/** Returns the .asl file source used to create this agent */
	public final String getASLSrc() {
		return aslSource;
	}
	
	public void setASLSrc(String file) {
		if (file != null && file.startsWith("./")) file = file.substring(2);
		aslSource = file;
	}
	
	/** Adds beliefs and plans form an URL */
	public boolean parseAS(URL asURL) {
		try {
			parseAS(asURL.openStream());
			logger.fine("as2j: AgentSpeak program '" + asURL + "' parsed successfully!");
			return true;
		} catch (IOException e) {
			logger.log(Level.SEVERE, "as2j: the AgentSpeak source file '" + asURL + "' was not found!");
		} catch (ParseException e) {
			logger.log(Level.SEVERE, "as2j: parsing error in \"" + asURL + "\": " + e.getMessage());
		} catch (Exception e) {
			logger.log(Level.SEVERE, "as2j: error parsing \"" + asURL + "\"", e);
		}
		return false;
	}
	
	/** Adds beliefs and plans form a file */
	public boolean parseAS(File asFile) {
		try {
			parseAS(new FileInputStream(asFile));
			logger.fine("as2j: AgentSpeak program '" + asFile + "' parsed successfully!");
			return true;
		} catch (FileNotFoundException e) {
			logger.log(Level.SEVERE, "as2j: the AgentSpeak source file '" + asFile + "' was not found!");
		} catch (ParseException e) {
			logger.log(Level.SEVERE, "as2j: parsing error in \"" + asFile + "\": " + e.getMessage());
		} catch (Exception e) {
			logger.log(Level.SEVERE, "as2j: error parsing \"" + asFile + "\"", e);
		}
		return false;
	}
	
	public void parseAS(InputStream asIn) throws ParseException, JasonException {
		as2j parser = new as2j(asIn);
		parser.agent(this);
	}
	
	public void parseAS(Reader asIn) throws ParseException, JasonException {
		as2j parser = new as2j(asIn);
		parser.agent(this);
	}
	
	@SuppressWarnings("unchecked")
	public InternalAction getIA(String iaName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		if (iaName.charAt(0) == '.') {
			iaName = "jason.stdlib" + iaName;
		}
		if (iaName.equals(jason.stdlib.import_module.SHORT_NAME)) iaName = jason.stdlib.import_module.FULL_NAME;
		InternalAction objIA = internalActions.get(iaName);
		if (objIA == null) {
			Class iaclass = Class.forName(iaName);
			try {
				// check if the class has "create" method -- singleton implementation
				Method create = iaclass.getMethod("create", (Class[])null);
				objIA = (InternalAction)create.invoke(null, (Object[])null);
			} catch (Exception e) {
				objIA = (InternalAction)iaclass.newInstance();
			}
			internalActions.put(iaName, objIA);
		}
		return objIA;
	}
	
	public void initDefaultFunctions() {
		if (functions == null) functions = new HashMap<String, ArithFunction>();
		addFunction(Count.class, false);
	}
	
	/** register an arithmetic function implemented in Java */
	public void addFunction(Class<? extends ArithFunction> c) {
		addFunction(c, true);
	}
	
	/** register an arithmetic function implemented in Java */
	private void addFunction(Class<? extends ArithFunction> c, boolean user) {
		try {
			ArithFunction af = c.newInstance();
			String error = null;
			if (user) error = FunctionRegister.checkFunctionName(af.getName());
			if (error != null) logger.warning(error);
			else functions.put(af.getName(), af);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Error registering function " + c.getName(), e);
		}
	}
	
	/** register an arithmetic function implemented in AS (by a rule, literal, or internal action) */
	public void addFunction(String function, int arity, String literal) {
		try {
			String error = FunctionRegister.checkFunctionName(function);
			if (error != null) logger.warning(error);
			else functions.put(function, new RuleToFunction(literal, arity, this));
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Error registering function " + literal, e);
		}
	}
	
	/**
	 * get the object the implements the arithmetic function <i>function/arity</i>, either global (like math.max) or
	 * local (like .count).
	 */
	public ArithFunction getFunction(String function, int arity) {
		if (functions == null) return null;
		ArithFunction af = functions.get(function);
		if (af == null || !af.checkArity(arity))
		// try global function
		af = FunctionRegister.getFunction(function, arity);
		if (af != null && af.checkArity(arity)) return af;
		else return null;
	}
	
	/** register exported predicates */
	public final void addExport(PredicateIndicator newExport) {
		exports.add(newExport);
	}
	
	public final void addExports(Collection<PredicateIndicator> newExports) {
		exports.addAll(newExports);
	}
	
	/** @return a new unique module name */
	public String getUniqueModuleName(String name) {
		String uname = name;
		int i = 1;
		while (modules.containsKey(uname)) {
			uname = (name + '_') + (++i);
		}
		return uname;
	}
	
	/** registers a module of an agent */
	public Module addModule(String name, String importedUnderModuleName, String path, Agent module, String reference, Token token) throws ParseException {
		Module oldMod, newMod;
		if ((oldMod = modules.put(name, newMod = new Module(importedUnderModuleName, path, module.exports, module, reference))) != null) {
			throw new ParseException("\n\t" + reference + "A module has been already imported with the name '" + name + "' at \n\t" + oldMod.reference, token);
		}
		modulesImported.put(path, newMod);
		return newMod;
	}
	
	public Module addModule(String name, String importedUnderModuleName, Module interimMod, String reference, Token token) throws JasonException, ParseException {
		Module newMod, oldOldMod;
		if ((oldOldMod = modules.put(name, newMod = new Module(importedUnderModuleName, interimMod, reference, token))) != null) {
			throw new ParseException("\n\t" + reference + "A module has been already imported with the name '" + name + "' at \n\t" + oldOldMod.reference, token);
		}
		return newMod;
	}
	
	public Module getModuleImported(String path) {
		checkModulesImported();
		return modulesImported.get(path);
	}
	
	private void checkModulesImported() {
		if (modulesImported == null) { // base agent
			modulesImported = new TreeMap<String, Module>();
			String ownPath;
			try {
				ownPath = Import.getAbsPathAndStream(getASLSrc(), getASLSrc(), false).fst;
				modulesImported.put(ownPath, baseAgent = new Module(ownPath));
				//				importChain.add(ownPath);
			} catch (Exception e) {
				// it should never happen, because of false-ed needStream
				e.printStackTrace();
			}
		}
	}
	
	public final void propagateForImported(Agent agent) {
		modulesImported = agent.modulesImported;
		baseAgent = agent.baseAgent;
	}
	
	public final void replaceModule(String path, Module module) {
		modulesImported.put(path, module);
	}
	
	public final Module getModule(String moduleName, String reference, Token token) throws ParseException {
		if (moduleName.isEmpty()) {
			checkModulesImported();
			if (baseAgent.exports == null) try {
				baseAgent = new Module("", baseAgent, reference, token);
			} catch (JasonException e) {
				throw new ParseException(reference + "There was a problem parsing the base Agent!", e);
			}
			return baseAgent;
		}
		return modules.get(moduleName);
	}
	
	/**
	 * @return the baseAgent
	 */
	public final Module getBaseAgent() {
		return baseAgent;
	}
	
	public final void setImportQueue(PriorityQueue<Import.Importer> importQueue) {
		this.importQueue = importQueue;
	}
	
	public final PriorityQueue<Import.Importer> getImportQueue() {
		return importQueue;
	}
	
	/**
	 * Registers the path as a newly applied module.
	 * 
	 * @param path
	 *            the absolute path of the module
	 * @return true: if the modules is truly newly registered - false: if the module has been already applied
	 */
	public final boolean moduleApplied(String path) {
		if (isAtomic()) {
			if (modulesApplied.contains(path)) return false;
			return modulesAppliedDelta.add(path);
		} else {
			return modulesApplied.add(path);
		}
	}
	
	private void modulesAtomicClear() {
		modulesAppliedDelta.clear();
	}
	
	private void modulesAtomicCommit() {
		modulesApplied.addAll(modulesAppliedDelta);
		modulesAtomicClear();
	}
	
	/**
	 * @return the importChain
	 */
	public final TreeSet<String> getImportChain() {
		return importChain;
	}
	
	/**
	 * @param importChain
	 *            the importChain to set
	 */
	public final void setImportChain(TreeSet<String> importChain) {
		this.importChain = importChain;
	}
	
	public final boolean isDoImports() {
		return doImports;
	}
	
	public final void setDoImport(boolean doImports) {
		this.doImports = doImports;
	}
	
	/**
	 * @return whether the agent was created by the side-kick parser
	 */
	public final boolean isSideKick() {
		return sideKick;
	}
	
	/**
	 * @param sideKick
	 *            whether the agent was created by the side-kick parser
	 */
	public final void setSideKick(boolean sideKick) {
		this.sideKick = sideKick;
	}
	
	public final int getLevel() {
		return level;
	}
	
	public final void setLevel(int level) {
		this.level = level;
	}
	
	/**
	 * @return the importedUnderModuleName
	 */
	public final String getImportedUnderModuleName() {
		return importedUnderModuleName;
	}
	
	/**
	 * @param importedUnderModuleName
	 *            the importedUnderModuleName to set
	 */
	public final void setImportedUnderModuleName(String importedUnderModuleName) {
		this.importedUnderModuleName = importedUnderModuleName;
	}
	
	public void addInitialGoal(Literal g) {
		initialGoals.add(g);
	}
	
	public void addInitialBel(Literal b) {
		initialBels.add(b);
	}
	
	public List<Literal> getInitialBels() {
		return initialBels;
	}
	
	/**
	 * @return the initial goals of the agent
	 */
	public List<Literal> getInitialGoals() {
		return initialGoals;
	}
	
	/** add the initial beliefs in BB and produce the corresponding events */
	public void addInitialBelsInBB() throws RevisionFailedException {
		// Once beliefs are stored in a Stack in the BB, insert them in inverse order
		for(int i = initialBels.size() - 1; i >= 0; i--) {
			Literal b = initialBels.get(i);
			
			// if l is not a rule and has free vars (like l(X)), convert it into a rule like "l(X) :- true."
			if (!b.isRule() && !b.isGround()) b = new Rule(b, Literal.LTrue);
			
			b.apply(new Unifier()); // to solve arithmetic expressions
			
			// does not do BRF for rules (and so do not produce events +bel for rules)
			if (b.isRule()) getBB().add(b);
			else addBel(b);
		}
		initialBels.clear();
	}
	
	public void addInitialGoalsInTS(List<Literal> initialGoals) {
		for(Literal g : initialGoals) {
			g.makeVarsAnnon();
			if (!g.hasSource()) g.addAnnot(BeliefBase.TSelf);
			getTS().getC().addAchvGoal(this, g, Intention.EmptyInt);
		}
	}
	
	/**
	 * Imports beliefs, plans and initial goals from another agent. Initial beliefs and goals are stored in
	 * "initialBels" and "initialGoals" lists but not included in the BB / TS. The methods addInitialBelsInBB and
	 * addInitialGoalsInTS should be called in the sequel to add those beliefs and goals into the agent.
	 * 
	 * @throws ParseException
	 */
	public void importComponents(Agent a, boolean dynamically) throws JasonException, ParseException {
		if (a != null) {
			if (dynamically) {
				for(Literal b : a.initialBels) {
					this.addBel(b); // has to be New Focus!!
					//this.addBel(b, ts.getC().SI);
				}
				addInitialGoalsInTS(a.initialGoals);
			} else {
				for(Literal b : a.initialBels) {
					this.addInitialBel(b);
				}
				for(Literal g : a.initialGoals) {
					this.addInitialGoal(g);
				}
			}
			
			for(Plan p : a.getPL()) {
				this.getPL().add(p);
			}
			functions.putAll(a.functions);
		}
	}
	
	/** Adds the prefix of a module to the things it needs them. It's used when importing a module. */
	public void applyModulePrefix(Agent parentAgent, String moduleName, String modulePrefix) {
		if (modulePrefix != null) {
			for(Literal b : initialBels) {
				ASSyntax.applyModulePrefixToTerm(parentAgent, moduleName, modulePrefix, b);
			}
			for(Literal g : initialGoals) {
				ASSyntax.applyModulePrefixToTerm(parentAgent, moduleName, modulePrefix, g);
			}
			for(Plan p : getPL()) {
				ASSyntax.applyModulePrefixToTerm(parentAgent, moduleName, modulePrefix, p);
			}
			Map<String, ArithFunction> newFunctions = new HashMap<String, ArithFunction>();
			for(Map.Entry<String, ArithFunction> e : functions.entrySet()) {
				ASSyntax.applyModulePrefixToArithFunction(parentAgent, moduleName, modulePrefix, e.getValue());
				newFunctions.put(modulePrefix + e.getKey(), e.getValue());
			}
			functions = newFunctions;
		}
	}
	
	/**
	 * Follows the default implementation for the agent's message acceptance relation and selection functions
	 */
	public boolean socAcc(Message m) {
		return true;
	}
	
	public Event selectEvent(Queue<Event> events) {
		// make sure the selected Event is removed from 'events' queue
		return events.poll();
	}
	
	public Option selectOption(List<Option> options) {
		if (options != null && !options.isEmpty()) {
			return options.remove(0);
		} else {
			return null;
		}
	}
	
	public Intention selectIntention(Queue<Intention> intentions) {
		// make sure the selected Intention is removed from 'intentions'
		// and make sure no intention will "starve"!!!
		return intentions.poll();
	}
	
	public Message selectMessage(Queue<Message> messages) {
		// make sure the selected Message is removed from 'messages'
		return messages.poll();
	}
	
	public ActionExec selectAction(List<ActionExec> actList) {
		// make sure the selected Action is removed from actList
		// (do not return suspended intentions)
		synchronized (actList) {
			Iterator<ActionExec> i = actList.iterator();
			while (i.hasNext()) {
				ActionExec a = i.next();
				if (!a.getIntention().isSuspended()) {
					i.remove();
					return a;
				}
			}
		}
		return null;
	}
	
	/** TS Initialisation (called by the AgArch) */
	public void setTS(TransitionSystem ts) {
		this.ts = ts;
		setLogger(ts.getUserAgArch());
		logger.setLevel(ts.getSettings().logLevel());
	}
	
	public TransitionSystem getTS() {
		return ts;
	}
	
	public void setBB(BeliefBase bb) {
		this.bb = bb;
	}
	
	public BeliefBase getBB() {
		return bb;
	}
	
	public void setPL(PlanLibrary pl) {
		this.pl = pl;
	}
	
	public PlanLibrary getPL() {
		return pl;
	}
	
	public DeltaBeliefBase getDBB() {
		return dbb;
	}
	
	/**
	 * @return the isAtomic
	 */
	public final boolean isAtomic() {
		return atomic;
	}
	
	public void atomicAdded() {
		if (atomic) {
			dbb.clear();
			pl.deltaClear();
			ts.getC().deltaClear();
			modulesAtomicClear();
			
			logger.log(Level.SEVERE, "Another atomic intention has been added!", new Exception());
		} else {
			atomic = true;
		}
	}
	
	public void atomicRemoved() throws RevisionFailedException {
		if (atomic) {
			atomicCommit();
		} else {
			logger.warning("Atomic intention has been already removed!");
		}
	}
	
	public void atomicDiscarded() {
		if (atomic) {
			atomic = false;
			dbb.clear();
			pl.deltaClear();
			ts.getC().deltaClear();
			modulesAtomicClear();
		}/* else {
			logger.warning("Atomic intention has been already discarded!");
			}*/
	}
	
	private void atomicCommit() throws RevisionFailedException {
		atomic = false;
		
		for(Triple<Literal, Literal, Intention> e : dbb.getEntries()) {
			List<Literal>[] result = brf(e.fst, e.snd, e.trd);
			if (result != null && ts != null) {
				ts.updateEvents(result, Intention.EmptyInt); // has to be New Focus!!
				/*
				boolean b0, b1;
				if(e.fst != null && e.snd != null && (b0 = e.fst.hasAnnot(BeliefBase.TNewFocus)) != (b1 = e.snd.hasAnnot(BeliefBase.TNewFocus))) {
					List[] r = new List[2];
					r[0] = result[0];
					r[1] = Collections.emptyList();
					ts.updateEvents(r, b0 ? Intention.EmptyInt : e.trd);
					
					r = new List[2];
					r[0] = Collections.emptyList();
					r[1] = result[1];
					ts.updateEvents(r, b1 ? Intention.EmptyInt : e.trd);
				} else {
					boolean bNewFocus = (e.fst != null && e.fst.hasAnnot(BeliefBase.TNewFocus)) || (e.snd != null && e.snd.hasAnnot(BeliefBase.TNewFocus));
					ts.updateEvents(result, bNewFocus ? Intention.EmptyInt : e.trd);
				}
				*/
			}
		}
		dbb.clear();
		
		pl.deltaCommit();
		ts.getC().deltaCommit();
		modulesAtomicCommit();
	}
	
	/** Belief Update Function: adds/removes percepts into belief base */
	public void buf(List<Literal> percepts) {
		if (percepts == null) {
			return;
		}
		
		// deleting percepts in the BB that is not perceived anymore
		Iterator<Literal> perceptsInBB = getBB().getPercepts();
		while (perceptsInBB.hasNext()) {
			Literal l = perceptsInBB.next();
			
			// could not use percepts.contains(l), since equalsAsTerm must be
			// used (to ignore annotations)
			boolean wasPerceived = false;
			Iterator<Literal> ip = percepts.iterator();
			while (ip.hasNext()) {
				Literal t = ip.next();
				
				// if perception t is already in BB
				if (l.equalsAsStructure(t) && l.negated() == t.negated()) {
					wasPerceived = true;
					// remove in percepts, since it already is in BB 
					// [can not be always removed, since annots in this percepts should be added in BB
					//  Jason team for AC, for example, use annots in perceptions]
					if (!l.hasAnnot()) ip.remove();
					break;
				}
			}
			if (!wasPerceived) {
				// new version (it is sure that l is in BB, only clone l when the event is relevant)
				perceptsInBB.remove(); // remove l as perception from BB
				
				Trigger te = new Trigger(TEOperator.del, TEType.belief, l);
				if (ts.getC().hasListener() || pl.hasCandidatePlan(te)) {
					l = l.copy();
					l.clearAnnots();
					l.addAnnot(BeliefBase.TPercept);
					te.setLiteral(l);
					ts.getC().addEvent(new Event(te, Intention.EmptyInt));
				}
				
				/*
				// old version
				// can not delete l, but l[source(percept)]
				l = (Literal)l.clone();
				l.clearAnnots();
				l.addAnnot(BeliefBase.TPercept);
				if (bb.remove(l)) {
				    ts.updateEvents(new Event(new Trigger(TEOperator.del, TEType.belief, l), Intention.EmptyInt));
				}
				*/
			}
		}
		
		// BUF only adds a belief when appropriate
		// checking all percepts for new beliefs
		for(Literal lp : percepts) {
			try {
				lp = lp.copy().forceFullLiteralImpl();
				lp.addAnnot(BeliefBase.TPercept);
				if (getBB().add(lp)) {
					Trigger te = new Trigger(TEOperator.add, TEType.belief, lp);
					ts.updateEvents(new Event(te, Intention.EmptyInt));
				}
			} catch (Exception e) {
				logger.log(Level.SEVERE, "Error adding percetion " + lp, e);
			}
		}
	}
	
	/**
	 * Returns true if BB contains the literal <i>bel</i> (using unification to test). The unifier <i>un</i> is updated
	 * by the method.
	 */
	public boolean believes(LogicalFormula bel, Unifier un) {
		try {
			Iterator<Unifier> iun = bel.logicalConsequence(this, un, 0);
			if (iun != null && iun.hasNext()) {
				un.compose(iun.next());
				return true;
			}
		} catch (Exception e) {
			logger.log(Level.SEVERE, "** Error in method believes(" + bel + "," + un + ").", e);
		}
		return false;
	}
	
	/**
	 * Find a literal in BB using unification to test.
	 * 
	 * Returns the belief as it is in BB, e.g. findBel(a(_),...) may returns a(10)[source(ag)].
	 * 
	 * The unifier <i>un</i> is updated by the method.
	 */
	public Literal findBel(Literal bel, Unifier un) {
		Iterator<Literal> relB = bb.getCandidateBeliefs(bel, un);
		if (relB != null) {
			while (relB.hasNext()) {
				Literal b = relB.next();
				
				// recall that order is important because of annotations!
				if (!b.isRule() && un.unifies(bel, b)) {
					return b;
				}
			}
		}
		return null;
	}
	
	/**
	 * This function should revise the belief base with the given literal to add, to remove, and the current intention
	 * that triggered the operation.
	 * 
	 * <p>
	 * In its return, List[0] has the list of actual additions to the belief base, and List[1] has the list of actual
	 * deletions; this is used to generate the appropriate internal events. If nothing change, returns null.
	 */
	@SuppressWarnings("unchecked")
	public List<Literal>[] brf(Literal beliefToAdd, Literal beliefToDel, Intention intention) throws RevisionFailedException {
		if (atomic) {
			dbb.newEntry(beliefToAdd, beliefToDel, intention);
			return null;
		}
		
		// This class does not implement belief revision! It
		// is supposed that a subclass will do it.
		// It simply add/del the belief.
		
		List<Literal>[] result = null;
		try {
			if (beliefToAdd != null) {
				if (logger.isLoggable(Level.FINE)) logger.fine("adding belief " + beliefToAdd);
				
				if (getBB().add(beliefToAdd)) {
					result = new List[2];
					result[0] = Collections.singletonList(beliefToAdd);
					result[1] = Collections.emptyList();
				}
			}
			
			if (beliefToDel != null) {
				Unifier u = null;
				try {
					u = intention.peek().unif; // get from current intention
				} catch (Exception e) {
					u = new Unifier();
				}
				
				if (logger.isLoggable(Level.FINE)) logger.fine("removing " + beliefToDel + " in BB=" + believes(beliefToDel, u));
				if (believes(beliefToDel, u)) {
					beliefToDel.apply(u);
					if (getBB().remove(beliefToDel)) {
						if (logger.isLoggable(Level.FINE)) logger.fine("Removed:" + beliefToDel);
						if (result == null) {
							result = new List[2];
							result[0] = Collections.emptyList();
						}
						result[1] = Collections.singletonList(beliefToDel);
					}
				}
				
			}
		} catch (Exception e) {
			logger.log(Level.WARNING, "Error at BRF.", e);
		}
		return result;
	}
	
	/**
	 * Adds <i>bel</i> in belief base (calling brf) and generates the events. If <i>bel</i> has no source, add
	 * <code>source(self)</code>. (the belief is not cloned!)
	 */
	public boolean addBel(Literal bel) throws RevisionFailedException {
		if (!bel.hasSource()) {
			bel.addAnnot(BeliefBase.TSelf);
		}
		List<Literal>[] result = brf(bel, null, Intention.EmptyInt);
		if (result != null && ts != null) {
			ts.updateEvents(result, Intention.EmptyInt);
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Adds <i>bel</i> in belief base (calling brf) and generates the events from the given Intention. If <i>bel</i> has
	 * no source, add <code>source(self)</code>. (the belief is not cloned!)
	 */
	public boolean addBel(Literal bel, Intention i) throws RevisionFailedException {
		if (!bel.hasSource()) {
			bel.addAnnot(BeliefBase.TSelf);
		}
		List<Literal>[] result = brf(bel, null, i);
		if (result != null && ts != null) {
			boolean bNewFocus = !ts.getSettings().sameFocus() || bel.hasAnnot(BeliefBase.TNewFocus);
			ts.updateEvents(result, bNewFocus ? Intention.EmptyInt : i);
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * If the agent believes in <i>bel</i>, removes it (calling brf) and generate the event.
	 */
	public boolean delBel(Literal bel) throws RevisionFailedException {
		if (!bel.hasSource()) {
			bel.addAnnot(BeliefBase.TSelf);
		}
		List<Literal>[] result = brf(null, bel, Intention.EmptyInt);
		if (result != null && ts != null) {
			ts.updateEvents(result, Intention.EmptyInt);
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Removes all occurrences of <i>bel</i> in BB. If <i>un</i> is null, an empty Unifier is used.
	 */
	public void abolish(Literal bel, Unifier un) throws RevisionFailedException {
		List<Literal> toDel = new ArrayList<Literal>();
		if (un == null) un = new Unifier();
		Iterator<Literal> il = getBB().getCandidateBeliefs(bel, un);
		if (il != null) {
			while (il.hasNext()) {
				Literal inBB = il.next();
				if (!inBB.isRule()) {
					// need to clone unifier since it is changed in previous iteration
					if (un.clone().unifiesNoUndo(bel, inBB)) {
						toDel.add(inBB);
					}
				}
			}
		}
		
		for(Literal l : toDel) {
			delBel(l);
		}
	}
	
	private void checkCustomSelectOption() {
		for(Method m : this.getClass().getMethods()) {
			if (!m.getDeclaringClass().equals(Agent.class) && m.getName().equals("selectOption")) {
				hasCustomSelOp = true;
				return;
			}
		}
		hasCustomSelOp = false;
	}
	
	public boolean hasCustomSelectOption() {
		return hasCustomSelOp;
	}
	
	static DocumentBuilder builder = null;
	
	/** Gets the agent "mind" (Beliefs, plans and circumstance) as XML */
	public Document getAgState() {
		if (builder == null) {
			try {
				builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			} catch (Exception e) {
				logger.log(Level.SEVERE, "Error creating XML builder\n");
				return null;
			}
		}
		Document document = builder.newDocument();
		document.appendChild(document.createProcessingInstruction("xml-stylesheet", "href='http://jason.sf.net/xml/agInspection.xsl' type='text/xsl' "));
		
		Element ag = getAsDOM(document);
		document.appendChild(ag);
		
		ag.appendChild(ts.getC().getAsDOM(document));
		return document;
	}
	
	@Override
	public String toString() {
		return "Agent " + getASLSrc();
	}
	
	/** Gets the agent "mind" as XML */
	public Element getAsDOM(Document document) {
		Element ag = (Element)document.createElement("agent");
		ag.setAttribute("name", ts.getUserAgArch().getAgName());
		ag.setAttribute("cycle", "" + ts.getUserAgArch().getCycleNumber());
		
		ag.appendChild(bb.getAsDOM(document));
		// ag.appendChild(ps.getAsDOM(document));
		return ag;
	}
	
	/** Gets the agent program (Beliefs and plans) as XML */
	public Document getAgProgram() {
		if (builder == null) {
			try {
				builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			} catch (Exception e) {
				logger.log(Level.SEVERE, "Error creating XML builder\n");
				return null;
			}
		}
		Document document = builder.newDocument();
		Element ag = (Element)document.createElement("agent");
		if (getASLSrc() != null && getASLSrc().length() > 0) {
			ag.setAttribute("source", getASLSrc());
		}
		ag.appendChild(bb.getAsDOM(document));
		ag.appendChild(pl.getAsDOM(document));
		document.appendChild(ag);
		
		return document;
	}
	
	public static final class Module {
		public static final String OPERATOR = "::";
		public static final char DOT = '*';
		
		private final String name;
		private final String reference;
		private final String path;
		private final String prefix;
		private final HashSet<PredicateIndicator> exports;
		private final Agent agent;
		
		Module(String path) { // used only for level 0 (base) agents (--> not for modules, hence when an agent indirectly/directly imports itself)
			this.name = null; // fine -> overwritten
			this.reference = null; // fine -> overwritten
			this.path = path;
			this.prefix = "";
			this.exports = null; // need to be changed! -> whenever it is requested need to generate it from path
			this.agent = null; // fine -> shows that no need to apply module prefixes 
		}
		
		Module(String name, String path, HashSet<PredicateIndicator> exports, Agent agent, String reference) {
			this.name = name;
			this.reference = reference;
			this.path = path;
			this.prefix = ('_' + path + OPERATOR).replace('.', DOT);
			this.exports = exports;
			this.agent = agent;
		}
		
		Module(String name, Module newMod, String reference, Token token) throws JasonException, ParseException {
			this.name = name;
			this.reference = reference;
			this.path = newMod.path;
			this.prefix = newMod.prefix;
			
			if (newMod.exports == null) { // level 0 agent is imported --> parse it for exports only
				Pair<String, InputStream> absPathAndStream;
				try {
					absPathAndStream = Import.getAbsPathAndStream(path, path, true);
				} catch (FileNotFoundException e) {
					throw new ParseException(reference + "The imported module's file '" + path + "' (base Agent) was not found! (It is being imported with module name '" + name + "'.)", token);
				} catch (Exception e) {
					throw new ParseException(reference + "The imported module's file '" + path + "' (base Agent) could not be imported! (It is being imported with module name '" + name + "'.)", token);
				}
				
				String parsePath = absPathAndStream.fst;
				InputStream in = absPathAndStream.snd;
				
				try {
					Agent ag = new Agent();
					ag.initAg();
					ag.setDoImport(false); // --> no modularity fields required to be set
					
					ag.setASLSrc(parsePath);
					as2j parser = new as2j(in);
					parser.agent(ag);
					this.exports = ag.exports;
					this.exports.addAll(ASSyntax.BASE_EXPORTS);
				} catch (ParseException e) {
					e.setIgnoreSubsequentMessages(true);
					throw e;
				} catch (TokenMgrError e) {
					e.setIgnoreSubsequentMessages(true);
					throw e;
				}
			} else {
				this.exports = newMod.exports;
			}
			this.agent = newMod.agent;
		}
		
		public HashSet<PredicateIndicator> getExports() {
			return exports;
		}
		
		public String getName() {
			return name;
		}
		
		public String getPath() {
			return path;
		}
		
		public Agent getAgent() {
			return agent;
		}
		
		public String getPrefix() {
			return prefix;
		}
		
		@Override
		public String toString() {
			return name + "=\"" + path + "\"";
		}
	}
	
}
